// Copyright (C) 2022 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<广西木子科技有限公司>，授权编号：<NT220402151538>，代码指纹：<A.648883738.885>，技术对接人微信：<ID: Lingmuziyi>】
// 【授权寄送：<收件：李先生、地址：南宁市科园西十路11号国电智能大厦1101F、电话：17736659550、邮箱：yingshashou@vip.qq.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.eva.framework;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.dto.DataFromClient;
import com.eva.framework.dto.DataFromServer;
import com.eva.framework.utils.LoggerFactory;
import com.x52im.rainbowchat.exception.TokenException;

/**
 * 服务端http框架的MVC控制器父类，用于捕获客客户端的http请求并调
 * 度业务处理器（Processor），遵从MVC框架设计原则。
 * <p>
 * <b>MVC调用层级: </b>
 * {@link #service(HttpServletRequest, HttpServletResponse)}
 * -> {@link #process(HttpServletRequest, HttpServletResponse)}
 * -> {@link #dispatch(DataFromClient, HttpServletRequest, HttpServletResponse)}.
 * <p>
 * <b>MVC调用原理: </b>
 * <pre>
 * -------------------------------------------- MVC框架的HTTP处理逻辑流程 ----------------------------------------------
 * http请求 => HttpPortal子类(MVC控制器) -> Processor子类(处理器) -> JobDispatcher方法(作业调度器) -> Action代码段(动作)
 *                                               |                        |                                |
 *                                         [processor_id]         [job_dispatch_id]                   [action_id]
 * --------------------------------------------------------------------------------------------------------------------
 * </pre>
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 */
public abstract class HttpController extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * 本项目部置到服务端上去后的物理磁盘根目录
     */
    public static String SERVER_PATH = "";

    /**
     * 本Servlet实例化时的初始化方法.
     *
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        SERVER_PATH = config.getServletContext().getRealPath("/");
        super.init(config);
        LoggerFactory.getLog().info("Hello EVA.EPC,init ok... ");
    }

    /**
     * 响应客端请求的主方法(本方法由Web容器调用).
     *
     * @param req HttpServletRequest
     * @param res HttpServletResponse
     * @throws ServletException
     * @throws IOException
     * @see HttpServlet
     */
    @Override
    public void service(HttpServletRequest req, HttpServletResponse res) throws
            ServletException, IOException {
        try {
            //调用核心处理方法
            process(req, res);
        } catch (Exception e) {
//			e.printStackTrace();
//			throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 核心处理方法.<br>
     * <p>
     * 本方法会确保处理完成后写回一个DataFromServer对象（客户端通过它可知是否处理成功等信息）给客端，如果处理成功则
     * DataFromServer对象中的sucess字值会被设置成true且returnValue会设置成处理完成的返回的对象，否则sucess将被设置成
     * false且returnValue里存放的将是错误消息文本（该文本不能保证一定不是null）.
     * </p>
     * <pre>
     * 	注：子类的处理实现推荐overload方法processImpl(DataFromClient, HttpServletRequest, HttpServletResponse)
     * ，一般情况下不推荐重写本方法.
     * </pre>
     *
     * @param req HttpServletRequest 客户端请求HttpServletRequest对象
     * @param res HttpServletResponse 客户端响应HttpServletResponse对象
     * @throws Exception 本方法调用中出现任何异常时都会抛出
     * @see #processImpl(DataFromClient, HttpServletRequest, HttpServletResponse)
     * @see DataFromServer
     */
    protected void process(HttpServletRequest req, HttpServletResponse res) throws Exception {
        //服务端是否要写回数据给客户端（本字段对应于DataFromClient中的doInput字段并且意义一样）
        boolean needRecieve = true;

        //处理完成后将要返回给客端的数据对象（通过它可知是否处理成功等信息）
        DataFromServer objFromServer = new DataFromServer();
        try {
            LoggerFactory.getLog().debug(">>S 客户端Cookie=" + req.getRequestedSessionId() + " ip=" + req.getRemoteHost() + ":" + req.getRemotePort());
            LoggerFactory.getLog().debug(String.format(">>S 线程名称::%s,进入时间::%s", Thread.currentThread().getName(), System.nanoTime()));

            //第1/3步：读取PC客户端发过来的数据
            DataFromClient dataFromClient = (DataFromClient) recieveFromClient(req);
            //* 服务端是否要写回数据给客户端
            needRecieve = dataFromClient.isDoInput();

            String notoken = req.getHeader("notoken");
            if (!CommonUtils.isStringEmpty(notoken)) {
                dataFromClient.setNotoken(notoken);
            }

            //第2/3步：提交处理器进行业务处理
            Object processedData = dispatch(dataFromClient, req, res);
            //处理成功了
            objFromServer.setSuccess(true);
            objFromServer.setReturnValue(String.valueOf(processedData));
        } catch (TokenException aa) {
            objFromServer.setSuccess(false);
            objFromServer.setReturnValue(aa.getMessage());
            LoggerFactory.getLog().warn(aa.getMessage(), aa);
            throw aa;
        } catch (Exception e) {
            objFromServer.setSuccess(false);
            // 2014-05-05日邮js改成只返回简单错误信息，而非完整Exception对象，目的是在跨平台实现时反射成JSON时不会StackOverflowError
            objFromServer.setReturnValue(CommonUtils.getExceptionStackTraceForPlainText(e));//e);
            LoggerFactory.getLog().warn(e.getMessage(), e);
            throw e;
        } finally {
            //* 服务端需要写回数据给客户端
            if (needRecieve) {
                //第3/3步：将处理完成的数据对象发回给客户端
                sendToClient(res, objFromServer);
            }
            LoggerFactory.getLog().debug(">>E 处理完成,客户端" + " ip=" + req.getRemoteHost() + ":" + req.getRemotePort()
                    + " 的请处理成功了?" + objFromServer.isSuccess() + (needRecieve ?
                    " - ret=" + objFromServer.getReturnValue().getClass()//objFromServer.getReturnValue() 直接输出返回值用于早期MVC框架调试时，生产环境下将大幅影响系统性能
                    : " - [doInput=false,无需返回值给客户端]"));
        }
    }

    /**
     * 将数据对象发回给PC客户端.
     *
     * @param res           客户端响应HttpServletResponse对象
     * @param objFromServer 要发送的可系列化的数据对象
     * @throws Exception 如果在操作OutputStream时出现异常
     */
    protected void sendToClient(HttpServletResponse res, DataFromServer objFromServer)
            throws IOException {
        ObjectOutputStream objOut = new ObjectOutputStream(res.getOutputStream());
        objOut.writeObject(objFromServer);
        objOut.flush();
        objOut.close();
    }

    /**
     * 读取PC客户端传过来的数据对象。
     *
     * @param req 客户端请求HttpServletRequest对象
     * @return 返回读取出来的客户端数据对象
     * @throws IOException            如果req.getInputStream()出现异常时抛出
     * @throws ClassNotFoundException 如果is.readObject()出现异常时抛出
     */
    protected DataFromClient recieveFromClient(HttpServletRequest req)
            throws IOException, ClassNotFoundException {
        ObjectInputStream is = new ObjectInputStream(req.getInputStream());
        DataFromClient ObjFromClient = (DataFromClient) is.readObject();
        is.close();
        return ObjFromClient;
    }

    /**
     * 客户端的请求处理实现.
     * <pre>
     * 	注：子类一般情况只需要重写此类即可达到要求。
     * </pre>
     *
     * @param objFromClient 这就是客户传过来的数据对象了（本方法中是必须的参数）
     * @param req           客户端请求实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
     * @param res           客户端响应实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
     * @return 处理完返回的对象（要给客户端的）
     * @throws Exception 本方法调用中出现任何异常时都会抛出
     */
    protected abstract Object dispatch(DataFromClient objFromClient, HttpServletRequest req
            , HttpServletResponse res) throws Exception;
}
